package com.dc.tool;

import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public class DigestAuthClient {

    private final String url;
    private final String username;
    private final String password;
    private final String method;
    private final Algorithm algorithm;

    private DigestAuthClient(Builder builder) {
        this.url = builder.url;
        this.username = builder.username;
        this.password = builder.password;
        this.method = builder.method;
        this.algorithm = builder.algorithm;
    }

    public enum Algorithm {
        MD5("MD5", "MD5"),
        MD5_SESS("MD5-sess", "MD5-sess"),
        SHA_256("SHA-256", "SHA-256"),
        SHA_256_SESS("SHA-256-sess", "SHA-256-sess"),
        SHA_512_256("SHA-512-256", "SHA-512-256"),
        SHA_512_256_SESS("SHA-512-256-sess", "SHA-512-256-sess");

        private final String desc;
        private final String value;

        Algorithm(String desc, String value) {
            this.desc = desc;
            this.value = value;
        }

        public String getDesc() {
            return desc;
        }

        public String getValue() {
            return value;
        }
    }

    public static class Builder {
        private String url;
        private String username;
        private String password;
        private String method;
        private Algorithm algorithm;

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder username(String username) {
            this.username = username;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        public Builder method(String method) {
            this.method = method;
            return this;
        }

        public Builder algorithm(Algorithm algorithm) {
            this.algorithm = algorithm;
            return this;
        }

        public DigestAuthClient build() {
            method = Optional.ofNullable(method)
                    .filter(StringUtils::hasText)
                    .orElse("POST");
            algorithm = Optional.ofNullable(algorithm).orElse(Algorithm.MD5);
            return new DigestAuthClient(this);
        }
    }

    public String execute() throws Exception {
        // Step 1: 发起初始请求
        System.out.println("First request");
        HttpURLConnection connection = createConnection(url, method);
        int statusCode = connection.getResponseCode();

        if (statusCode != 401) {
            return readResponse(connection);
        }

        System.out.println("Second request");
        // Step 2: 解析 WWW-Authenticate 头
        String wwwAuthHeader = connection.getHeaderField("WWW-Authenticate");
        System.out.println("wwwAuthHeader +> " + wwwAuthHeader);
        Map<String, String> authParams = parseWWWAuthenticate(wwwAuthHeader);

        String realm = authParams.get("realm");
        String nonce = authParams.get("nonce");
        String qop = authParams.get("qop");

        // Step 3: 构造 Authorization 头
        String cnonce = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        String nc = "00000001";

        String ha1;

        if (algorithm == Algorithm.MD5_SESS || algorithm == Algorithm.SHA_256_SESS || algorithm == Algorithm.SHA_512_256_SESS) {
            String a1 = username + ":" + realm + ":" + password;
            String a1Hash = digest(a1, algorithm.desc);
            ha1 = digest(a1Hash + ":" + nonce + ":" + cnonce, algorithm.desc);
        } else {
            ha1 = digest(username + ":" + realm + ":" + password, algorithm.desc);
        }

        String ha2 = digest(method + ":" + url, algorithm.desc);
        String response;

        if ("auth-int".equals(qop)) {
            response = digest(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2, algorithm.desc);
        } else {
            response = digest(ha1 + ":" + nonce + ":" + ha2, algorithm.desc);
        }

        String authHeader = String.format(
                "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", algorithm=\"%s\", qop=%s, nc=%s, cnonce=\"%s\", response=\"%s\"",
                username, realm, nonce, url, algorithm.value, qop, nc, cnonce, response
        );
        System.out.println("authHeader +> " + authHeader);

        // Step 4: 发送带认证头的请求
        connection = createConnection(url, method);
        connection.setRequestProperty("Authorization", authHeader);

        return readResponse(connection);
    }

    private HttpURLConnection createConnection(String urlString, String method) throws IOException {
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod(method);
        connection.setDoInput(true);
        if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
            connection.setDoOutput(true);
        }
        return connection;
    }

    private String readResponse(HttpURLConnection connection) throws IOException {
        InputStream inputStream = connection.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        StringBuilder response = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            response.append(line).append("\n");
        }
        reader.close();
        String result = response.toString();
        System.out.println("result +> " + result);
        return result;
    }

    private Map<String, String> parseWWWAuthenticate(String header) {
        Map<String, String> params = new HashMap<>();
        String[] tokens = header.substring(7).split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
        for (String token : tokens) {
            String[] keyValue = token.split("=", 2);
            if (keyValue.length == 2) {
                String key = keyValue[0].trim();
                String value = keyValue[1].trim().replace("\"", "");
                params.put(key, value);
            }
        }
        return params;
    }

    private String digest(String data, String algorithm) {
        try {
            String algName = algorithm.toUpperCase();
            MessageDigest md;

            switch (algName) {
                case "MD5":
                    md = MessageDigest.getInstance("MD5");
                    break;
                case "SHA-256":
                    md = MessageDigest.getInstance("SHA-256");
                    break;
                case "SHA-512-256":
                    md = MessageDigest.getInstance("SHA-512/256");
                    break;
                default:
                    throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
            }

            byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}